<?php
if (!defined('APP_ID')) { die('No direct script access allowed'); }

/**
 * Main system class
 */
class Node {
	const VERSION = '0.0.3';

	// Prefixes
	const MODEL_PREFIX = 'Model_';
	const CONTROLLER_PREFIX = 'Controller_';

	// Enviroment constants
	const PRODUCTION = 0x1;
	const DEVELOPMENT = 0x2;

	// Seconds constants
	const SECONDS_IN_MINUTE = 60;	
	const SECONDS_IN_HOUR = 3600;	
	const SECONDS_IN_DAY = 86400;
	const SECONDS_IN_WEEK = 604800;

	private static $_enviroment = null;
	private static $_modules = array();
	private static $_paths = array();
	private static $_files = array();
	private static $_cache_files = false;
	private static $_start_cache_files = false;

	/**
	 * Execute system
	 */
	public static function execute() {
		try {
			// Setting exception handler for uncaught exceptions
			set_exception_handler(array('Node', 'exceptionHandler'));

			// Setting error handler for runtime errors
			set_error_handler(array('Node', 'errorHandler'));

			// Setting shutdown handler (handle fatal errors)
			register_shutdown_function(array('Node', 'shutdownHandler'));

			// Startup session for current application
			session_name(APP_ID);
			session_start();

			// Setting output buffer handler
			ob_start(array('Node', 'outputBufferHandler'));

			// Initiate and start routing
			Router::instance()->init()->startup();

			// Send headers
			Output::sendHeaders();

			// Output body
			Output::renderBody();

			// Cache system files
			if (self::$_cache_files && Config::get('enabled', 'cache', false)) {
				Cache::set('loaded_files', self::$_files);
			}

			// Send buffer to output handler
			ob_end_flush();
		} catch (Node_Exception $e) {
			if (Input::isCli()) {
				$cont = $e->getMessage();
			} else {
				$cont = View::factory('node/error')->set('exception', $e);
			}
			Output::body($cont);

			self::logError(
				'"'. get_class($e) . '" handled',
				false,
				$e->getCode(),
				$e->getMessage(),
				$e->getFile(),
				$e->getLine(),
				$e->getTraceAsString()
			);
		}
    }

	/**
	 * Initializing system
	 */
	public static function init($vars = array()) {
		// Modules
		if (isset($vars['modules']) && is_array($vars['modules'])) {
			foreach ($vars['modules'] as $module) {
				if (!empty($module)) {
					self::$_modules[] = strtolower($module);
				}
			}
		}

		// Preloading paths
		self::$_paths[] = APP_DIR;
		foreach (self::$_modules as $module) {
			self::$_paths[] = MOD_DIR . $module . DIR_SEP;
		}
		self::$_paths[] = SYS_DIR;

		// Start caching loaded files
		Config::load('cache');
		self::$_start_cache_files = true;
		
		// Load file list from cache
		if (Config::get('enabled', 'cache', false)) {
			$_files = Cache::get('loaded_files');
			if ($_files === null) {
				$_files = array();
			}
			self::$_files = array_merge($_files, self::$_files);
		}

		// Set default enviroment
		if (isset($vars['enviroment'])) {
			self::$_enviroment = $vars['enviroment'];
		} else {
			self::$_enviroment = Config::get('enviroment', null, self::DEVELOPMENT);
		}
	}

	/**
	 * Auto loading classes
	 */
	public static function autoLoad($class_name) {
		$result = self::loadClass($class_name);
		if (!$result) {
			self::log('Class "' . $class_name . '" not found.');
		}
	}

	/**
	 * Auto loading classes
	 */
	public static function loadClass($class_name) {
		$file_name = self::classNameToFileName($class_name);
        $file_path = self::findFile('classes', $file_name);
		if ($file_path) {
			require_once($file_path);
			return true;
		}
		return false;
	}

    /**
	 * Finding file
	 */
	public static function findFile($dir, $file) {
		$result = false;

		$dir = trim(strtolower($dir));
		if (in_array($dir, array('config','i18n'))) {
			$result = array();
			$file_path = $dir . DIR_SEP . self::fileNameToPath($file) . PHP_EXT;
			$_paths = array_reverse(self::$_paths);
			foreach ($_paths as $dir_path) {
				$file_name = $dir_path . $file_path;
				$file_hash = md5($file_name);
				if (isset(self::$_files[$file_hash])) {
					$result[] = $file_name;
				} else if (is_file($file_name)) {
					$result[] = $file_name;
					self::$_files[$file_hash] = $file_name;
					self::$_cache_files = self::$_start_cache_files;
				}
			}
		} else {
			$file_path = $dir . DIR_SEP . self::fileNameToPath($file) . PHP_EXT;
			foreach (self::$_paths as $dir_path) {
				$file_name = $dir_path . $file_path;
				$file_hash = md5($file_name);
				if (isset(self::$_files[$file_hash])) {
					$result = $file_name;
				} else if (is_file($file_name)) {
					$result = $file_name;
					self::$_files[$file_hash] = $file_name;
					self::$_cache_files = self::$_start_cache_files;
				}
			}
		}

		return $result;   
	}

	/**
	 * Class name to file path (without extension)
	 */
	public static function classNameToFileName($class_name) {
		return strtolower(str_replace('_', DIR_SEP, $class_name));
	}

	/**
	 * Class name to file path (without extension)
	 */
	public static function fileNameToPath($file_name) {
		return strtolower(str_replace(array(' ','_','-','.'), DIR_SEP, $file_name));
	}

	/**
	 * Error handling
	 */
	public static function errorHandler($code, $str, $file, $line) {
		self::logError(
			'Error handled',
			true,
			$code,
			$str,
			$file,
			$line
		);
	}

	/**
	 * Exception handling
	 */
	public static function exceptionHandler($e) {
		self::logError(
			'Uncaught "'. get_class($e) . '" handled',
			true,
			$e->getCode(),
			$e->getMessage(),
			$e->getFile(),
			$e->getLine(),
			$e->getTraceAsString()
		);
	}

	/**
	 * Shutdown catching unhandled errors
	 */
	public static function shutdownHandler() {
		$error = error_get_last();
		if (($error !== null)) {
			self::logError(
				'Error handled due shutdown',
				true,
				$error['type'],
				$error['message'],
				$error['file'],
				$error['line']
			);
		}
	}

	/**
	 * Shutdown catching unhandled errors
	 */
	public static function outputBufferHandler($buffer) {
		$error = error_get_last();
		if (($error !== null)) {
			self::logError(
				'Error handled due output buffer',
				true,
				$error['type'],
				$error['message'],
				$error['file'],
				$error['line']
			);
		}
		return false;
	}

	/**
	 * Logging
	 */
	public static function log($text, $display = true) {
		if (Config::get('logging')) {
			$now = time();
			$log_cont = '-------------------' . PHP_EOL;
			$log_cont .= date('Y-m-d H:i:s', $now) . PHP_EOL;
			$log_cont .= '-------------------' . PHP_EOL;
			$log_cont .= $text . PHP_EOL;
			$log_dir = DATA_DIR . 'logs' . DIR_SEP . trim(strtolower(APP_ID)) . DIR_SEP;
			$log_file = $log_dir . date('Y-m-d', $now) . '.log';
			if (!is_dir($log_dir)) {
				mkdir($log_dir);
			}
			file_put_contents($log_file, $log_cont, FILE_APPEND);
		}

		if ($display && (self::$_enviroment == self::DEVELOPMENT)) {
			if (Input::isCli()) {
				echo $text;
			} else {
				echo '<pre>', $text, '</pre>';
			}
		} else {
			echo 'Please try again later.' . PHP_EOL;
		}
	}

	/**
	 * Logging errors
	 */
	public static function logError($text, $display = false, $code = 0, $mess = '', $file = '', $line = '', $info = '') {
		$str = $text . PHP_EOL;
		$str .= '- Code: ' . $code . PHP_EOL;
		$str .= '- Message: ' . $mess . PHP_EOL;
		$str .= '- File: ' . $file . PHP_EOL;
		$str .= '- Line: ' . $line . PHP_EOL;
		$str .= $info . PHP_EOL;
		self::log($str, $display);
	}
}

// Alias for i18n::get method
function __() { return call_user_func_array(array('I18n', 'get'), func_get_args()); }
